#!/usr/bin/env bash
#
# ZFS backup script.
#
# Creates temporary ZFS snapshots, and 'zfs sends' them (or differences
# between them and previous snapshots) down fifo nodes in
# /etc/burp/fifos.
#
# On success, the ZFS script renames the temporary snapshot
# to a more permanent name so that the snapshots can be tracked consistently.
#
# To use, put something like this in your burp.conf.
# You can add multiple pools.
#
# backup_script=/etc/burp/zfs_script
# backup_script_arg=7
# backup_script_arg=rpool/export/home@burp
# backup_script_arg=rpool/ROOT/s10x_u9wos_14a/var@burp
# backup_script_post_run_on_fail=1
#
# A snapshot numbered between 1 to $MAX_SNAPS is created on a backup. The
# number is incremented for each backup. Number 1 is always a full ZFS snapshot.
# The rest are incrementals based on the previous number. After $MAX_SNAPS,
# the sequence starts from 1 again.
#
# You will want to configure the burp server to keep enough backups so that
# a ZFS 'full' snapshot is always available. For example, if you set $MAX_SNAPS
# to 7, you will want to keep at least 14 backups on the server to guarantee
# that you can always restore at least 7 backups into the past.
#
# To do a restore, you will need to do this (for example), starting from number
# 1:
# mkfifo /etc/burp/fifos/rpool/export/home@burp<number>
# cat /etc/burp/fifos/rpool/export/home@burp<number> | zfs recv <zfs recv args>
#
# Then repeat, each time incrementing the number until you reach the backup
# that you want to end up on.

if [ $# -lt 7 ] ; then
	echo "$0: not enough arguments" 1>&2
	exit 1
fi

MODE="$1" ; shift
FAILED="$1" ; shift; shift; shift; shift
MAX_SNAPS="$1" ; shift

if [ "$MODE" = "pre" ] ; then
	declare -a arr=($@)

	# Set up the fifo directory 
	PIPE_DIR=/etc/burp/fifos
	rm -rf "$PIPE_DIR" || exit 1
	mkdir -p "$PIPE_DIR" || exit 1

	# Create all the snapshots that we will need.
	a=0
	while [ $a -lt $# ] ; do
		SNAPSHOT="${arr[$a]}"
		SNAPSHOT_TMP="$SNAPSHOT"tmp

		# Find out how many snapshots already exist.
		inc=1
		while [ "$inc" -le $MAX_SNAPS ] ; do
		  /usr/sbin/zfs list "$SNAPSHOT$inc" >/dev/null 2>&1 || break
		  inc=$((inc+1))
		done

		if [ "$inc" -gt $MAX_SNAPS ] ; then
		  # Had enough snapshots now. Start from 1 again.
		  inc=1
		fi

		# Destroy all the unneeded snapshots
		i=$inc
		while [ "$i" -le $MAX_SNAPS ] ; do
		  /usr/sbin/zfs destroy "$SNAPSHOT$i" >/dev/null 2>&1
		  /usr/sbin/zfs destroy "$SNAPSHOT_TMP$i" >/dev/null 2>&1
		  i=$((i+1))
		done

		# Create the new snapshot.
		/usr/sbin/zfs snapshot -r "$SNAPSHOT_TMP$inc" || exit 1

		# Create a pipe.
		ZFS_PIPE=$PIPE_DIR/$SNAPSHOT$inc
		mkdir -p "${ZFS_PIPE%/*}"
		mkfifo "$ZFS_PIPE" || exit 1
		chmod 777 "$ZFS_PIPE" || exit 1

		a=$((a+1))
	done

	# Need to exec here, otherwise burp will wait forever for this script to
	# return.
	exec > /dev/null 2>&1

	# Now, go through the list and do zfs sends for all the new snapshots.
	a=0
	while [ $a -lt $# ] ; do
		SNAPSHOT="${arr[$a]}"
		SNAPSHOT_TMP="${arr[$a]}tmp"

		# Figure out the most recent snapshot again.
		inc=1
		while [ "$inc" -le $MAX_SNAPS ] ; do
		  /usr/sbin/zfs list "$SNAPSHOT$inc" >/dev/null 2>&1 || break
		  inc=$((inc+1))
		done

		ZFS_PIPE=$PIPE_DIR/$SNAPSHOT$inc

		if [ "$inc" = 1 ] ; then
		  # First in the chain. Just do the full zfs send.
		  /usr/sbin/zfs send "$SNAPSHOT_TMP$inc" > "$ZFS_PIPE" &
		else
		  # Not the first in the chain. Do an incremental zfs send.
		  /usr/sbin/zfs send -i "$SNAPSHOT$((inc-1))" "$SNAPSHOT_TMP$inc" > "$ZFS_PIPE" &
		fi

		a=$((a+1))
	done

	exit 0
elif [ "$MODE" = "post" ] ; then
 	while [ "$#" -gt 0 ] ; do
	  SNAPSHOT=$1 ; shift
	  SNAPSHOT_TMP="$SNAPSHOT"tmp
	  i=1
	  if [ "$FAILED" = "1" ] ; then
	    while [ "$i" -le $MAX_SNAPS ] ; do
	      /usr/sbin/zfs destroy "$SNAPSHOT_TMP$i" >/dev/null 2>&1 
	      i=$((i+1))
	    done
	  else
	    while [ "$i" -le $MAX_SNAPS ] ; do
	      /usr/sbin/zfs list "$SNAPSHOT_TMP$i" >/dev/null 2>&1 && \
			/usr/sbin/zfs rename "$SNAPSHOT_TMP$i" "$SNAPSHOT$i"
	      i=$((i+1))
	    done
	  fi
	done

	exit 0
else
	echo "$0: unknown mode: $MODE" 1>&2
	exit 1
fi
